﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Model;

namespace Server.Controller.Graph
{
    public enum GraphMode { BiDirectional, UniDirectional }

    public class GraphAlgoritme: IGraphAlgoritme
    {
        // List of stations
        private List<Station> vertices;
        // List of linked lists, containing routes FROM  a station
        private List<LinkedList<Route>> head;
        // List of linked lists, containing routes TO a station
        private List<LinkedList<Route>> tail;
        // mode can be Directional (one way/directed) or BiDirectional (two way)
        private GraphMode mode;

        public GraphAlgoritme(GraphMode mode)
        {
            vertices = new List<Station>();
            head = new List<LinkedList<Route>>();
            tail = new List<LinkedList<Route>>();
            this.mode = mode;
        }

        // Adds station to vertices list, and adds a new linkedList for head and tail lists.
        public void addStation(Station vertex)
        {
            vertices.Add(vertex);
            head.Add(new LinkedList<Route>());
            tail.Add(new LinkedList<Route>());
        }

        public void addRoute(int fromId, int toId, double distance, int time)
        {
            Route edge = new Route();
            edge.From = getVertexById(fromId);
            edge.To = getVertexById(toId);
            edge.Distance = distance;
            edge.Time = time;

            head[vertices.IndexOf(edge.From)].AddLast(edge);
            tail[vertices.IndexOf(edge.To)].AddLast(edge);
        }

        // Gets adjecent stations to a given station.
        public ICollection<Station> getAdjantVertices(Station vertex)
        {
            LinkedList<Station> list = new LinkedList<Station>();
            foreach (Route e in head[vertices.IndexOf(vertex)])
            {
                list.AddLast(e.To);
            }
            if (mode == GraphMode.UniDirectional)
            {
                foreach (Route e in tail[vertices.IndexOf(vertex)]) 
                {
                    list.AddLast(e.From);   
                }
            }
            return list;
        }

        // Returns an IEnumerator collection of stations (vertices). Is used to be able to 
        // iterate through the stations in the dijkstra algorithm.
        public IEnumerator<Station> Vertices()
        {
            return vertices.GetEnumerator();
        }

        // Get route between station a and b.
        public Route getEdge(Station v1, Station v2)
        {
            foreach (Route e in head[vertices.IndexOf(v1)])
            {
                if (e.To.Equals(v2))
                {
                    return e;
                }
            }
            return null;
        }

        // Returns a station by id.
        public Station getVertexById(int id)
        {
            foreach (Station v in vertices)
            {
                if (v.Id.Equals(id))
                {
                    return v;
                }
            }
            return null;
        }

        public ICollection<Station> Dijkstras(Station start, Station end)
        {
            PriorityQueueLinkedList<Station> pq = new PriorityQueueLinkedList<Station>();

            IEnumerator<Station> v = Vertices();
            while (v.MoveNext())
            {
                v.Current.Weight = double.PositiveInfinity;
                pq.queue(v.Current);
            }
            start.Weight = 0;
            while (!pq.isEmpty())
            {
                Station u = pq.dequeue();
                foreach (Station w in getAdjantVertices(u))
                {
                    relax(u, w);
                }

            }
            LinkedList<Station> result = new LinkedList<Station>();
            int endId = end.Id;
            if (end.Weight == double.PositiveInfinity)
            {
                throw new Exception("Route not complete!");
            }
            result.AddFirst(end);
            while (end.Predecessor != null)
            {
                result.AddFirst(end.Predecessor);
                end = end.Predecessor;
            }

            if (!result.First.Value.Id.Equals(start.Id) || !result.Last.Value.Id.Equals(endId))
            {
                throw new Exception("Route not complete!");
            } 
            return result;
        }

        private void relax(Station source, Station end)
        {
            Route e = getEdge(source, end);
            if ((e == null) && (mode == GraphMode.UniDirectional))
            {
                e = getEdge(end, source);
            }
            if (e == null)
            {
                throw new Exception("Cannot find Edge");
            }
            if ((end.Weight > source.Weight + e.Distance) || double.IsInfinity(end.Weight))
            {
                end.Weight = source.Weight + e.Distance;
                end.Predecessor = source;
            }
        }
    }
}
